#include "lib.h"
#include "page.h"

/*
 * follow global var are defined in mem.S
 *
 */
extern ptr_t TEXT_START;
extern ptr_t TEXT_END;
extern ptr_t TEXT_BOOT_START;
extern ptr_t TEXT_BOOT_END;
extern ptr_t DATA_START;
extern ptr_t DATA_END;
extern ptr_t RODATA_START;
extern ptr_t RODATA_END;
extern ptr_t BSS_START;
extern ptr_t BSS_END;
extern ptr_t HEAP_START;
extern ptr_t HEAP_SIZE;


static uint32_t _num_pages = 0;
static uint64_t _alloc_start;
static uint64_t _alloc_end;

static ptr_t _align_page(ptr_t addr)
{
	uint64_t mask =	(1 << PAGE_ORDER) - 1;
	return (addr + mask) & ~mask;
}

static void inline _set_flag(struct page_t *page)
{
	page->flags = PAGE_TAKEN;
}

static void inline _clear_flag(struct page_t *page)
{
	page->flags = PAGE_RELEASE;
}

static void inline _set_last_flag(struct page_t *page)
{
	page->flags |= PAGE_LAST;
}

static uint8_t inline _is_alloc(struct page_t *page)
{
	return (page->flags & PAGE_TAKEN) ? (uint8_t)1 : (uint8_t)0;
}

static uint8_t inline _is_free(struct page_t *page)
{
	return (page->flags == PAGE_RELEASE) ? (uint8_t)1 : (uint8_t)0;
}

static uint8_t inline _is_last(struct page_t *page)
{
	return (page->flags & PAGE_LAST) ? (uint8_t)1 : (uint8_t)0;
}

void *page_alloc(uint64_t npages)
{
	uint8_t hit = 0;
	struct page_t *save_page;
	struct page_t *page_entry = (struct page_t*)HEAP_START;

	for (uint32_t i = 0; i < _num_pages; i++) {
		if (_is_free(page_entry)) {
			/*
			 * meet a free page
			 */
			hit = (uint8_t)1;
			save_page = page_entry;
			/*
			 * continue to check if following
			 * (npages - 1) pages are also unallocated.
			 */
			for (uint32_t sec = i; sec < i + npages - 1; sec++) {
				page_entry++;
				if (_is_alloc(page_entry)) {
					hit = (uint8_t)0;
					break;
				}
			}

			/*
			 * get a memory block which is good enough for us
			 */
			if (hit == 1) {
				page_entry = save_page;
				for (uint32_t _3rd = i; _3rd < i + npages; _3rd++) {
					_set_flag(page_entry);
					page_entry++;
				}
				_set_last_flag(page_entry - 1);
			/*
			 * return the actual start
			 * address of the first page of this memory block
			 */
				return (void*)(_alloc_start + i * PAGE_SIZE);
			}

		}
		page_entry++;
	}

	return NULL;
}


void page_free(void *p)
{

	if (p == NULL || (ptr_t)p > _alloc_end)
		return;

	struct page_t *page_entry = (struct page_t*)HEAP_START;

	uint64_t npages = ((ptr_t)p - _alloc_start) / PAGE_SIZE;
	page_entry = page_entry + npages;

	while (_is_alloc(page_entry)) {
		if (_is_last(page_entry)) {
			_clear_flag(page_entry);
			break;
		} else {
			_clear_flag(page_entry);
		}
		page_entry++;
	}
}

void page_init()
{
	_num_pages = HEAP_SIZE / PAGE_SIZE - 8;
	mprintf("HEAP_START = %p, HEAP_SIZE = 0x%lx, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages);

	struct page_t *page = (struct page_t*)HEAP_START;
	for(int i = 0; i < _num_pages; i++) {
		_clear_flag(page);
		page++;
	}

	_alloc_start = _align_page(HEAP_START + 8 * PAGE_SIZE);
	_alloc_end = _alloc_start + (PAGE_SIZE * _num_pages);

	mprintf("TEXT_BOOT:	%p -> %p\n", TEXT_BOOT_START, TEXT_BOOT_END);
	mprintf("TEXT:   	%p -> %p\n", TEXT_START, TEXT_END);
	mprintf("RODATA: 	%p -> %p\n", RODATA_START, RODATA_END);
	mprintf("DATA:		%p -> %p\n", DATA_START, DATA_END);
	mprintf("BSS:		%p -> %p\n", BSS_START, BSS_END);
	mprintf("alloc:		%p -> %p\n", _alloc_start, _alloc_end);
}


/*
void page_test()
{
	void *p = page_alloc(2);
	mprintf("p = %p\n", p);
	//page_free(p);

	void *p2 = page_alloc(7);
	mprintf("p2 = %p\n", p2);
	page_free(p2);

	void *p3 = page_alloc(4);
	mprintf("p3 = %p\n", p3);
}
*/
